import type { FilterType } from '../types'
import { BaseDevice, type OpenUSBDevice } from '../BaseDevice'
import { calculateIIRFilterParameters } from './utils'

const FILTER_TYPE_MAP: Partial<Record<FilterType, number>> = {
  DISABLE: 0x00,
  LOW_SHELF_2: 0x01,
  PEAKING: 0x02,
  HIGH_SHELF_2: 0x03,
  LOW_PASS_2: 0x04,
  HIGH_PASS_2: 0x05,
}

type FILTER_TYPE_MAP_KEYS = keyof typeof FILTER_TYPE_MAP

const COMMAND = {
  WRITE: 0x01,
  READ: 0x80,
  SET_ACTIVE_EQ: 0x0F,
  SAVE_EQ_TO_FLASH: 0x01,
  CLEAR_FLASH: 0x05,
  ERASE_CONFIG: {
    ADC: 1,
    DAC: 2,
    EQ: 3,
    ALL: 4,
  },
  UPDATE_EQ: 0x09,
  UPDATE_EQ_COEFF: 0x0A,
} as const

export interface Filter {
  frequency: number
  q: number
  gain: number
  filterType?: string
}

export class MarigoldDevice extends BaseDevice<Filter[]> {
  private readonly bLength = 0x00 // Payload length, no need to set, leave it as 0

  constructor(openDevice: OpenUSBDevice) {
    super(openDevice, {
      localPreset: true,
      presetEQs: [
        {
          uuid: '0',
          title: '预设1',
          file: null,
        },
        {
          uuid: '1',
          title: '预设2',
          file: null,
        },
        {
          uuid: '2',
          title: '预设3',
          file: null,
        },
        {
          uuid: '3',
          title: '预设4',
          file: null,
        },
        {
          uuid: '4',
          title: '预设5',
          file: null,
        },
        {
          uuid: '5',
          title: '预设6',
          file: null,
        },
        {
          uuid: '6',
          title: '预设7',
          file: null,
        },
      ],
      productUUID: '111c767e-cb9d-4bdc-8001-8a0a0d3f1d97',
      reportId: 0x4B,
      peqIndex: 0x07, // 0x00 ~ 0x07, keep it as 0x07
    })
  }

  private async saveEQToFlash(): Promise<void> {
    await this.openDevice.sendDataNoResp(new Uint8Array([
      COMMAND.WRITE,
      COMMAND.SAVE_EQ_TO_FLASH,
      this.bLength,
      0x00,
    ]), this.config.reportId)

    // wait for 300ms
    await new Promise(resolve => setTimeout(resolve, 300))
  }

  protected async clearFlash(): Promise<void> {
    await this.openDevice.sendDataNoResp(new Uint8Array([
      COMMAND.WRITE,
      COMMAND.CLEAR_FLASH,
      this.bLength,
      COMMAND.ERASE_CONFIG.ALL,
    ]), this.config.reportId)
  }

  protected async readEQIndex(): Promise<number> {
    const data = await this.openDevice.sendData(new Uint8Array([
      COMMAND.READ,
      COMMAND.SET_ACTIVE_EQ,
      this.bLength,
    ]), this.config.reportId)

    if (!data)
      throw new Error('No data received for EQ index')

    return data.getUint8(3)
  }

  protected async writeEQIndex(index: number): Promise<void> {
    await this.openDevice.sendDataNoResp(new Uint8Array([
      COMMAND.WRITE,
      COMMAND.SET_ACTIVE_EQ,
      this.bLength,
      index,
    ]), this.config.reportId)

    await this.saveEQToFlash()
  }

  // read single PEQ data
  private async readPEQIndexData(filterIndex: number): Promise<Filter> {
    const data = await this.openDevice.sendData(new Uint8Array([
      COMMAND.READ,
      COMMAND.UPDATE_EQ,
      this.bLength,
      0x00,
      filterIndex,
    ]), this.config.reportId)

    if (!data)
      throw new Error('No data received for PEQ data')

    // convert DataView to Int8Array
    const filterData = new Int8Array(data.buffer)

    const frequency = ((filterData[28] & 0xFF) << 8) | (filterData[27] & 0xFF)
    const q = (filterData[30] & 0xFF) + ((filterData[29] & 0xFF) / 256)
    const gain = filterData[32] + ((filterData[31] & 0xFF) / 256)
    const filterType = Object.keys(FILTER_TYPE_MAP).find(
      key => FILTER_TYPE_MAP[key as FILTER_TYPE_MAP_KEYS] === filterData[33],
    )

    return {
      frequency,
      q,
      gain,
      filterType,
    }
  }

  // read all PEQ data
  protected async readPEQData(): Promise<Filter[]> {
    const filters: Filter[] = []
    const length = 8

    for (let i = 0; i < length; i++) {
      filters.push(await this.readPEQIndexData(i))
    }

    return filters
  }

  private async setPEQIndexData(
    filterIndex: number,
    filter: Filter,
  ): Promise<void> {
    const buffer = new Uint8Array(36)

    buffer[0] = COMMAND.WRITE
    buffer[1] = COMMAND.UPDATE_EQ
    buffer[2] = this.bLength

    // EQ索引和滤波器索引 (4字节)
    buffer[3] = 0x00
    buffer[4] = filterIndex
    buffer[5] = 0x00
    buffer[6] = 0x00

    // 写入滤波器系数 (20字节)
    const coeffs = calculateIIRFilterParameters(filter.frequency, filter.gain, filter.q)
    buffer.set(coeffs, 7)

    // 写入中心频率 (2字节)
    buffer[27] = filter.frequency & 0xFF
    buffer[28] = (filter.frequency >> 8) & 0xFF

    // 写入Q值 (2字节)
    buffer[29] = Math.round((filter.q % 1) * 256)
    buffer[30] = Math.floor(filter.q)

    // 写入增益 (2字节)
    buffer[31] = Math.round((filter.gain % 1) * 256)
    buffer[32] = Math.floor(filter.gain)

    // 写入滤波器类型 (1字节)
    if (filter.filterType && filter.filterType in FILTER_TYPE_MAP) {
      const filterTypeValue = FILTER_TYPE_MAP[filter.filterType as FILTER_TYPE_MAP_KEYS]
      buffer[33] = filterTypeValue !== undefined ? filterTypeValue : FILTER_TYPE_MAP.PEAKING!
    }
    else {
      buffer[33] = FILTER_TYPE_MAP.PEAKING!
    }

    // 写入增益偏置 (1字节)
    buffer[34] = 0x00 // no offset

    // 写入EQ编号 (1字节)
    buffer[35] = this.config.peqIndex

    await this.openDevice.sendDataNoResp(buffer, this.config.reportId)
  }

  protected async writePEQData(filters: Filter[]): Promise<void> {
    for (let i = 0; i < filters.length; i++) {
      await this.setPEQIndexData(i, filters[i])
    }

    await this.saveEQToFlash()
  }
}
